/**
 * @file pointers.cpp
 * @author Shevelev Mark (shevelev.mm@yandex.ru)
 * @brief  Примеры кодов для обсуждения темы <<Указатели>> 
 * @version 0.1
 * @date 2024-03-07
 * 
 * @copyright Copyright (c) 2024
 */

#include <iostream>

#define COMPILATION_ERROR_EXAMPLE 0

/**
 * Главное определение:
 * Указатели - это переменные хранящие адреса других данных.
 * 
 * Важно! Указатель - это переменная, следовательно указатель
 * имеет собственную память и на него распространяются все такие же 
 * правила, как и налюбую другую переменную (или константу).
 *
 * Отличительной особенностью указателя является то, что такая 
 * переменная хранит <<адрес>> - местоположение других данных
 * в памяти. Для этого в C++ на уровне синтаксиса и логики языка
 * выделены отдельные типы данных, которые обладают специальной 
 * операцией <<разыменование>> - взятие данных по адресу, хранящемуся
 * в памяти указателя.
 */

/**
 * Объявление указателей. 
 */
void pointers_declaration() {
    /**
     * Ниже представлено объявление указателя.
     * pointer_to_int - идентификатор переменной-указателя
     * символ '*' показывает компилятору, что pointer_to_int -
     * это именно указатель
     */

    int * pointer_to_int;

    /**
     * Важно! При объявлении указателя символ '*' нужно ставить 
     * у каждого идентификатора, который ялвяется указателем!
     */
    
    // два указателя
    int *a_ptr, *b_ptr;

    // указатель и целое число
    int *i_ptr, i;

    // целое число и указатель
    int j, *j_ptr;

    /**
     * Символ '*' может стоять и возле типа данных, но
     * указателем будет только первый идентификатор!
     */
    int* q_intptr, q_int;
}

/**
 * Отдельно стоит обратить внимание, что при использовании
 * переименования типов с помощью typedef / using объявление
 * будет распространяться на все переменные, а не только на 
 * первый идентификатор, но и на последующие.
 */
#include <typeinfo>

typedef int* iptr_t;

void typedef_pointer_example() {
    // два указателя
    iptr_t a, b; 
    std::cout 
        << typeid(a).name() << " " 
        << typeid(b).name() << std::endl;
}

using uiptr_t = unsigned*;

void using_poiter_example() {
    // два указателя
    uiptr_t a, b;
    std::cout 
        << typeid(a).name() << " " 
        << typeid(b).name() << std::endl;
}

void address_to_pointer() {
    int i;
    int *i_ptr = &i;

    unsigned j;
    unsigned *j_ptr = &j;

    float f;
    auto *f_ptr = &f;

    long l;
    auto l_ptr = &l;

    std::cout 
        << typeid(f_ptr).name() << " "
        << typeid(l_ptr).name() << std::endl;
}

#undef COMPILATION_ERROR_EXAMPLE
#define COMPILATION_ERROR_EXAMPLE 0
void wrong_pointer_type() {
    short s = 1;

    // неявные преобразования между типами
    int   i = s;
    long  l = i;

    // неявные преобразования указателей запрещены
#if 1 == COMPILATION_ERROR_EXAMPLE
    int *i_ptr = &s;
    long *l_ptr = &i;
#endif
}

/**
 * Пример использования операции разыменования (косвенного обращения)
 * для чтения данных 
 */
void dereference_example() {
    int i = 5;
    int *i_ptr = &i;
    std::cout << *i_ptr << std::endl;
}
/**
 * Пример изменения данных косвенным образом 
 */
void inderect_modification() {

    int i = 5, *i_ptr = &i;

    std::cout << "Prev: " << i << '\n';

    *i_ptr = 7;

    std::cout << "New: " << i << '\n';

    std::cout << std::endl;
}

/**
 *  
 * 
 * 
 */
 #undef COMPILATION_ERROR_EXAMPLE
 #define COMPILATION_ERROR_EXAMPLE 0
void pointer_to_constant_bad() {
    int const cv_i = 42;
#if 1 == COMPILATION_ERROR_EXAMPLE
    int *i_ptr = &cv_i;
#endif
}

#undef COMPILATION_ERROR_EXAMPLE
#define COMPILATION_ERROR_EXAMPLE 0
void pointer_to_constant_good() {

    int const cv_i = 42;

    int const *i_ptr;

    i_ptr = &cv_i;

    std::cout << *i_ptr << std::endl;

#if 1 == COMPILATION_ERROR_EXAMPLE
    *i_ptr = 7;
    std::cout << cv_i << std::endl;
#endif
}

void variable_pointer() {
    /**
     * После инициализации мы можем присвоить указателю 
     * другой адрес. 
     */
    int i = 5, j = 7;

    int *ptr = &i; // инициализация
    std::cout << *ptr << '\n';

    ptr = &j; // присваиваем новый адрес
    std::cout << *ptr << '\n';

    std::cout << std::endl;
}

#undef COMPILATION_ERROR_EXAMPLE
#define COMPILATION_ERROR_EXAMPLE 0
void constant_pointer() {
    int i = 5, j = 9;
    std::cout << i << '\n';

    int * const i_ptr = &i;
    std::cout << *i_ptr << '\n';

    *i_ptr = 7;
    std::cout << i << '\n';

#if 1 == COMPILATION_ERROR_EXAMPLE
    i_ptr = &j;
    std::cout << *i_ptr << '\n';
#endif

    std::cout << std::endl;

    int const * const j_ptr = &j;
}

/**
 * В языке C не было ссылок. Единственным способом косвенного  
 * обращения к данным были указатели. Ниже представлен пример
 * функции обмена данными между двумя числами посредством
 * указателей.
 */
void swap_with_pointers(int * const a, int * const b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void swap_with_pointers_test() {
    int x = 1, z = 5;
    std::cout << "(x,z) = " << x << " " << z << '\n';

    swap_with_pointers(&x, &z);
    std::cout << "(x,z) = " << x << " " << z << '\n';

    std::cout << std::endl;
}

void sizeof_pointer_and_ref() {
    char c = 'a';
    char &c_ref = c;
    char *c_ptr = &c;
    std::cout << sizeof(c) << " " << sizeof(c_ref) << " " << sizeof(c_ptr) << '\n';

    int i = 5;
    int &i_ref = i;
    int *i_ptr = &i;
    std::cout << sizeof(i) << " " << sizeof(i_ref) << " " << sizeof(i_ptr) << '\n';

    std::cout << std::endl;
}

void change_pointer() {
    int i = 5, j = 7;
    int *ptr; // не инициализируем
    ptr = &i; // запоминаем адрес
    std::cout << *ptr << '\n';

    ptr = &j; // запоминаем другой адрес
    std::cout << *ptr << '\n';

    std::cout << std::endl;
}

void reference_to_rvalue(int const &ref) {
    std::cout << ref << std::endl;
}

void pointer_to_rvalue(int const *ptr) {
    std::cout << *ptr << std::endl;
}

void rvalue_example() {
    int i = 5, j = 7; // (i + j) -> rvalue // временное значение

    reference_to_rvalue(i + j);
//    pointer_to_rvalue(&(i + j));
}

#undef COMPILATION_ERROR_EXAMPLE
#define COMPILATION_ERROR_EXAMPLE 0
void nullptr_example() {
#if 1 == COMPILATION_ERROR_EXAMPLE
    {
        auto null_ptr = NULL;
        int *ptr = null_ptr;
    }
#endif

    {
        auto null_ptr = nullptr;
        int *ptr = null_ptr;
    }
}

void overload_f(long l) {
    std::cout << "Long: " << l << std::endl;
}

void overload_f(int *ptr) {
    std::cout << "Pointer: " << ptr << std::endl;
}

void overload_with_null_nullptr() {
    overload_f(NULL);
    overload_f(nullptr);

    int *i = nullptr;
    if (i) {
        std::cout << "Hello world" << std::endl;
    }
}

void void_ptr_example() {
    void *ptr;

    int i = 5;
    ptr = &i;

    unsigned u = 7u;
    ptr = &u;
}

void reinterpret_cast_example() {
    void *ptr;

    int i = 5;
    ptr = &i;

    int *i_ptr = reinterpret_cast<int *>(ptr);

    std::cout << *i_ptr << std::endl;
}

void aliasing_example() {
    int i = 5;
    void *ptr = &i;

    int *i_ptr  = reinterpret_cast<int*>(ptr);
    float *f_ptr = reinterpret_cast<float*>(ptr);
}

void aliasing_not(int * p1, float *p2);

void aliasing_yes(int * p1, unsigned const *p2);

void byte_example() {
    std::byte b{15};   // 0x0F
    b <<= 4;           // 0xF0
    // b += std::byte{1};         // ошибка компиляции
    b |= std::byte{1}; // 0xF0 | 0x01
    std::cout << std::to_integer<unsigned>(b) << std::endl;
}

void pointer_arithmetic_example() {
    int i = 5;
    int *i_ptr = &i;
    std::cout << i_ptr << " " << (i_ptr - 1) << '\n';

    std::byte b{1};
    std::byte *b_ptr = &b;
    std::cout << b_ptr << " " << (b_ptr - 1) << '\n';

    double d = 1.0;
    double *d_ptr = &d;
    std::cout << d_ptr << " " << (d_ptr - 1) << '\n';
    
    std::cout << std::endl;
}

void pointer_arithmetic_ub() {
    int arr[4] = {10, 20, 30, 40};
    int *ptr = &(arr[0]);   // получаем адрес первого элемента

    int *ptr3 = ptr + 3;    // Ок! Получаем адрес последнего элемента
    int *ptr4 = ptr + 4;    // Ok! Первый элемент за концом данных, туда можно сдвинуть указатель
    int a = *ptr;           // Ок! Можно прочитать данные из первого элемента
    int b = *ptr3;          // Ok! Можно прочитать данные из последнего элемента
    bool g = 
        (ptr3 + 1) == ptr4; // Ок! Результат определён и указатели должно быть равны

    int *ptr5 = ptr + 5;    // НП! Результат сдвига неизвестен!
    int c = *ptr4;          // НП! Нельзя читать или писать данные за пределы памяти объекта
    int *ptr5b = ptr4 + 1;  // НП! Результат сдвига неизвестен!
    bool s = ptr5 == ptr5b; // НП! Так как в 385 и в 387 неопределённое поведение, то результат сравнения не известен
}

void pointers_comparison() {
    int arr[4] = {10, 20, 30 ,40};
    int *first_address = &(arr[0]);
    int *second_address = &(arr[1]);
    std::cout << std::boolalpha
        << "f < s " << (first_address <  second_address) << '\n'
        << "f > s " << (first_address > second_address) << '\n'
        << "f == s " << (first_address == second_address) << '\n'
        << "f != s " << (first_address != second_address) << '\n'
        << "f + 1 == s " << (first_address + 1 == second_address) << '\n'
        << "f + 2 < s " << (first_address + 2 < second_address) << '\n'
        << "f + 2 > s " << (first_address + 2 > second_address) << '\n';
}

void pointers_comparison_ub() {
    int i = 5;
    int j = 6;
    int *i_ptr = &i, *j_ptr = &j;
    std::cout << (j_ptr > i_ptr) << " " << (i_ptr > j_ptr) << std::endl;
    if (j_ptr > i_ptr) {
        std::cout << j_ptr << " " << (i_ptr + 1) << " " << (j_ptr == i_ptr + 1) << std::endl;
    } else {
       std::cout << j_ptr << " " << (i_ptr + 1) << " " << (j_ptr + 1 == i_ptr) << std::endl; 
    }
}

void array_walk_with_pointers() {
    int arr[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};

    for (int *ptr = &(arr[0]), *past_the_end = ptr + 10; ptr != past_the_end; ++ptr)
        std::cout << *ptr << " | ";

    std::cout << std::endl;
}

void ptrdiff_example() {
   int arr[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; 

   int *ptr_first = &(arr[0]); // первый элемент
   int *ptr_last  = &(arr[9]); // последний элемент

   ptrdiff_t diff_last_first = ptr_last - ptr_first;
   ptrdiff_t diff_first_last = ptr_first - ptr_last;

   std::cout << diff_last_first << " " << diff_first_last;

   std::cout << std::endl;
}

void size_ptrdiff_limits() {
    std::cout 
        << "size_t [" 
            << std::numeric_limits<size_t>::min() << ", " 
            << std::numeric_limits<size_t>::max() << "] " << sizeof(size_t) << "\n"
        << "ptrdiff_t [" 
            << std::numeric_limits<ptrdiff_t>::min() << " , " 
            << std::numeric_limits<ptrdiff_t>::max() << "] " << sizeof(ptrdiff_t) << "\n"
        << std::endl;
}

void swap_4bytes(void *lha, void *rha) {
    std::byte *lha_base = reinterpret_cast<std::byte*>(lha);
    std::byte *rha_base = reinterpret_cast<std::byte*>(rha);
    
    for (std::size_t offset = 0; offset != 4; ++offset) {
        auto tmp = *(lha_base + offset);
        *(lha_base + offset) = *(rha_base + offset);
        *(rha_base + offset) = tmp;
    }
}

void swap_4bytes_test() {
    int a = 5, b = 7;
    swap_4bytes(&a, &b);
    std::cout << "(a, b) " << a << " " << b << '\n';

    char arr[4] = {'a', 'b', 'c', 'd'};
    char brr[4] = {'1', '2', '3', '4'};
    swap_4bytes(&(arr[0]), &(brr[0]));
    std::cout << brr[0] << " " << brr[1] << " " << brr[2] << " " << brr[3] << '\n';

    unsigned long l = 65535lu;
    swap_4bytes(&l, reinterpret_cast<std::byte*>(&l) + 4);
    std::cout << l << '\n';
}

unsigned element_copy_cnt = 0u;
void copy_element(void *dst, void const *src, std::size_t element_byte_size) {
    auto dst_int = reinterpret_cast<int*>(dst);
    auto src_int = reinterpret_cast<int const *>(src);
    *dst_int = *src_int;
    ++element_copy_cnt;
}

void copy_element_test() {
    int const src[4] = {1, 2, 3, 4};
    int dst[4] = {0};
    copy_many_elements(&(dst[0]), &(src[0]), 4u, sizeof(int));
    assert(1 == dst[0]);
    assert(2 == dst[1]);
    assert(3 == dst[2]);
    assert(4 == dst[3]);
    assert(4 == element_copy_cnt);
}


int main() {
    if (false) typedef_pointer_example();
    if (false) using_poiter_example();
    if (false) address_to_pointer();
    if (false) wrong_pointer_type();
    if (false) dereference_example();
    if (false) inderect_modification();
    if (false) pointer_to_constant_bad();
    if (false) pointer_to_constant_good();
    if (false) variable_pointer();
    if (false) constant_pointer();
    if (false) swap_with_pointers_test();
    if (false) sizeof_pointer_and_ref();
    if (false) change_pointer();
    if (false) rvalue_example();
    if (false) nullptr_example();
    if (false) overload_with_null_nullptr();
    if (false) void_ptr_example();
    if (false) reinterpret_cast_example();
    if (false) byte_example();
    if (false) pointer_arithmetic_example();
    if (false) pointers_comparison();
    if (false) pointers_comparison_ub();
    if (false) array_walk_with_pointers();
    if (false) ptrdiff_example();
    if (false) size_ptrdiff_limits();
    if (true) swap_4bytes_test();
    return 0;
}